package be.rivendale.mathematics;

import be.rivendale.geometry.AxisAlignedBoundingBox;
import be.rivendale.mathematics.intersection.Intersection;
import org.junit.Test;

import static be.rivendale.mathematics.MathematicalAssert.*;
import static be.rivendale.mathematics.intersection.RayIntersectionMode.afterPassThroughPoint;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class TriangleTest {
	private static final Triangle DEFAULT_TRIANGLE = new Triangle(new Vertex(1, -2, 3), new Vertex(-5, 8, 6), new Vertex(3, 5, 8));

	@Test
    public void constructorSetsAllPoints() throws Exception {
        Triangle triangle = new Triangle(new Vertex(1, 2, 3), new Vertex(-4, 8, 12), new Vertex(7, 8, 9));
        assertEquals(new Vertex(1, 2, 3), triangle.getA());
        assertEquals(new Vertex(-4, 8, 12), triangle.getB());
        assertEquals(new Vertex(7, 8, 9), triangle.getC());
    }

    @Test(expected = IllegalArgumentException.class)
    public void constructorThrowsIllegalArgumentExceptionWhenPointAIsNull() throws Exception {
        new Triangle(null, new Vertex(4, 5, 6), new Vertex(7, 8, 9));
    }

    @Test(expected = IllegalArgumentException.class)
    public void constructorThrowsIllegalArgumentExceptionWhenPointBIsNull() throws Exception {
        new Triangle(new Vertex(1, 2, 3), null, new Vertex(7, 8, 9));
    }

    @Test(expected = IllegalArgumentException.class)
    public void constructorThrowsIllegalArgumentExceptionWhenPointCIsNull() throws Exception {
        new Triangle(new Vertex(1, 2, 3), new Vertex(4, 5, 6), null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void constructorThrowsIllegalArgumentExceptionWhenAllPointAreLinear() throws Exception {
        new Triangle(new Vertex(1, 2, 3), new Vertex(4, 5, 6), new Vertex(7, 8, 9));
    }

    @Test(expected = IllegalArgumentException.class)
    public void constructorThrowsIllegalArgumentExceptionWhenPointAAndPointBAreEqual() throws Exception {
        new Triangle(new Vertex(1, 2, 3), new Vertex(1, 2, 3), new Vertex(7, 8, 9));
    }

    @Test(expected = IllegalArgumentException.class)
    public void constructorThrowsIllegalArgumentExceptionWhenPointAAndPointCAreEqual() throws Exception {
        new Triangle(new Vertex(1, 2, 3), new Vertex(4, 5, 6), new Vertex(1, 2, 3));
    }

    @Test(expected = IllegalArgumentException.class)
    public void constructorThrowsIllegalArgumentExceptionWhenPointBAndPointCAreEqual() throws Exception {
        new Triangle(new Vertex(1, 2, 3), new Vertex(7, 8, 9), new Vertex(7, 8, 9));
    }

    @Test
    public void normalReturnsTheNormalOfTheTriangle() throws Exception {
		Vector normal = DEFAULT_TRIANGLE.normal();
        assertVectorEquals(new Vertex(29, 36, -62), normal);
    }

	@Test
    public void normalLeavesStateOfTriangleUnchanged() throws Exception {
		DEFAULT_TRIANGLE.normal();
        assertPointEquals(new Vertex(1, -2, 3), DEFAULT_TRIANGLE.getA());
        assertPointEquals(new Vertex(-5, 8, 6), DEFAULT_TRIANGLE.getB());
        assertPointEquals(new Vertex(3, 5, 8), DEFAULT_TRIANGLE.getC());
    }

    @Test
    public void intersectionReturnsIntersectionObject() throws Exception {
        Triangle triangle = new Triangle(new Vertex(1, 2, 3), new Vertex(-4, 1, 2), new Vertex(3, -3, -2));
        Ray ray = new Ray(new Triple(1, 3, -2), new Triple(0, 2, 1));
        Intersection intersection = triangle.intersection(ray, afterPassThroughPoint);
        assertTrue(intersection.doesIntersect());
    }

	@Test(expected = IllegalArgumentException.class)
	public void intersectionThrowsIllegalArgumentExceptionWhenRayIsNull() {
		Triangle triangle = new Triangle(new Vertex(1, 2, 3), new Vertex(-4, 1, 2), new Vertex(3, -3, -2));
		triangle.intersection(null, afterPassThroughPoint);
	}

	@Test(expected = IllegalArgumentException.class)
	public void intersectionThrowsIllegalArgumentExceptionWhenIntersec() {
		Triangle triangle = new Triangle(new Vertex(1, 2, 3), new Vertex(-4, 1, 2), new Vertex(3, -3, -2));
		Ray ray = new Ray(new Vertex(0, 1, 2), new Vertex(3, 4, 5));
		triangle.intersection(ray, null);
	}

	@Test
	public void getAxisAlignedBoundingBoxReturnsABoundingBoxAroundTheTriangle() {
	    AxisAlignedBoundingBox axisAlignedBoundingBox = DEFAULT_TRIANGLE.getAxisAlignedBoundingBox();
		assertPointEquals(new Triple(-5, -2, 3), axisAlignedBoundingBox.getMinimumBound());
		assertPointEquals(new Triple(3, 8, 8), axisAlignedBoundingBox.getMaximumBound());
	}

	@Test
	public void getAxisAlignedBoundingBoxHasCorrectMinimumBoundXValue() {
		Triangle triangle = new Triangle(new Triple(1, 1, 1), new Triple(-1, 0, 0), new Triple(2, 0, 0));
		assertRealEquals(-1, triangle.getAxisAlignedBoundingBox().getMinimumBound().getX());
	}

	@Test
	public void getAxisAlignedBoundingBoxHasCorrectMinimumBoundYValue() {
		Triangle triangle = new Triangle(new Triple(1, -1, 0), new Triple(0, -2, 0), new Triple(0, 0, -3));
		assertRealEquals(-2, triangle.getAxisAlignedBoundingBox().getMinimumBound().getY());
	}

	@Test
	public void getAxisAlignedBoundingBoxHasCorrectMinimumBoundZValue() {
		Triangle triangle = new Triangle(new Triple(1, 1, -1), new Triple(0, -3, 0), new Triple(0, 0, -2));
		assertRealEquals(-2, triangle.getAxisAlignedBoundingBox().getMinimumBound().getZ());
	}

	@Test
	public void getAxisAlignedBoundingBoxHasCorrectMaximumBoundXValue() {
		Triangle triangle = new Triangle(new Triple(1, 1, 1), new Triple(-1, 0, 0), new Triple(2, 0, 0));
		assertRealEquals(2, triangle.getAxisAlignedBoundingBox().getMaximumBound().getX());
	}

	@Test
	public void getAxisAlignedBoundingBoxHasCorrectMaximumBoundYValue() {
		Triangle triangle = new Triangle(new Triple(1, 5, 1), new Triple(0, 6, 0), new Triple(0, 7, 8));
		assertRealEquals(7, triangle.getAxisAlignedBoundingBox().getMaximumBound().getY());
	}

	@Test
	public void getAxisAlignedBoundingBoxHasCorrectMaximumBoundZValue() {
		Triangle triangle = new Triangle(new Triple(1, 50, 10), new Triple(0, 60, 11), new Triple(0, 0, 12));
		assertRealEquals(12, triangle.getAxisAlignedBoundingBox().getMaximumBound().getZ());
	}

	/**
	 * This would yield an axis aligned bounding box with no depth (and thus not a cuboid but a rectangle).
	 */
	@Test
	public void axisAlignedBoundingBoxCanBeRetrievedIfATriangleLiesEntirelyOnOnePlane() {
		Triangle triangleThatLiesEntirelyOnTheXYPlane = new Triangle(new Triple(1, -1, 1), new Triple(0, 1, 1), new Triple(-1, -1, 1));
		assertPointEquals(new Triple(-1, -1, 1), triangleThatLiesEntirelyOnTheXYPlane.getAxisAlignedBoundingBox().getMinimumBound());
		assertPointEquals(new Triple(1, 1, 1), triangleThatLiesEntirelyOnTheXYPlane.getAxisAlignedBoundingBox().getMaximumBound());
	}
}
